Effective C++ 札记

变量常量

  1. 避免使用#define,而使用常量、枚举和内联函数,enum比const好用
  2. 出于效率原因,C++不保证内置类型(int等基本类型)数据成员的初始化。对于成员变量(class A)的内置类型, 会在构造函数进入之前进行初始化; 在构造函数前给出初始化列表来确保初始化,例如:C():b(), i(){}
  3. 静态变量的生命周期不同于栈或者堆中的对象,从它被构造开始一直存在,直到程序结束。 包括全局变量、命名空间下的变量、类中和函数中定义的static对象。 其中,定义在函数中的称为 local static,其他的称为 non-local static。

函数

  1. 编译器默认定义函数调用时机:

    • 构造函数:对象定义;使用其他兼容的类型初始化对象时(可使用 explicit 来避免这种情况)
    • 复制构造函数:用一个对象来初始化另一对象时;传入对象参数时;返回对象时;
    • 析构函数:作用域结束(包括函数返回)时;delete
    • =运算符:一个对象赋值给另一对象
    • 示例:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      Empty e1;               // 默认构造函数
      Empty e2(e1); // 拷贝构造函数
      Empty e3 = e1; // 拷贝构造函数
      e2 = e1; // = 运算符

      void func(Empty e){ // 拷贝构造函数,拷贝一份参数对象
      return e; // 拷贝构造函数,拷贝一份返回对象
      // 析构函数,拷贝得到的参数对象被析构
      }

      e2 = func(e1); // = 运算符
      // 析构函数,返回值被析构
  2. 通过把默认定义函数声明成private而且不实现它,可以禁用这些函数被调用,可以用于实现单例;也可以用base class的方式来禁用

  3. 析构函数声明为虚函数的目的在于以基类指针调用析构函数时能够正确地析构子类部分的内存。 否则子类部分的内存将会泄漏。示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    // 基类
    class TimeKeeper{
    public:
    virtual ~TimeKeeper();
    ...
    };
    TimeKeeper *ptk = getTimeKeeper(): // 可能返回任何一种子类
    ...
    delete ptk;
  4. 由于析构函数常常被自动调用,在析构函数中抛出的异常往往会难以捕获,引发程序非正常退出或未定义行为,所以析构函数不要抛出异常

  5. 使用对象来管理资源,类似智能指针